import numpy as np
import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox

class HMMSegmenterTagger:
    def __init__(self):
        # 分词状态
        self.seg_states = ['B', 'M', 'E', 'S']
        
        # 分词模型参数
        self.seg_A = {}  # 分词状态转移概率
        self.seg_B = {}  # 分词发射概率
        self.seg_P = {}  # 分词初始状态概率
        
        # 存储训练数据中的词性信息
        self.char_pos = {}  # 字符到词性的映射
        
        self.init_parameters()
    
    def init_parameters(self):
        # 初始化分词模型参数
        for state in self.seg_states:
            self.seg_A[state] = {s: 0.0 for s in self.seg_states}
            self.seg_B[state] = {}
            self.seg_P[state] = 0.0
    
    def train(self, file_path):
        state_count = {s: 0 for s in self.seg_states}
        trans_count = {s: {t: 0 for t in self.seg_states} for s in self.seg_states}
        emit_count = {s: {} for s in self.seg_states}
        
        try:
            # 尝试不同的编码方式
            encodings = ['utf-8', 'gbk', 'gb2312']
            for encoding in encodings:
                try:
                    with open(file_path, 'r', encoding=encoding) as f:
                        lines = f.readlines()
                        break
                except UnicodeDecodeError:
                    continue
            else:
                raise UnicodeDecodeError("无法用支持的编码方式读取文件")
            
            # 处理文件内容
            current_tags = []
            current_chars = []
            pos_tags = []  # 存储词性标注
            
            for line in lines:
                if line.strip():  # 非空行
                    parts = line.strip().split()
                    if len(parts) >= 2:  # 确保行包含字符和标签
                        char, tag = parts[0], parts[1]
                        current_chars.append(char)
                        current_tags.append(tag)
                        
                        # 存储字符的词性标注
                        if len(parts) >= 3:  # 如果有词性标注
                            pos_tag = parts[2]
                            self.char_pos[char] = pos_tag
                            pos_tags.append(pos_tag)
                        
                        # 统计发射概率
                        if char not in emit_count[tag]:
                            emit_count[tag][char] = 0
                        emit_count[tag][char] += 1
                        state_count[tag] += 1
                        
                        # 统计转移概率
                        if len(current_tags) > 1:
                            prev_tag = current_tags[-2]
                            trans_count[prev_tag][tag] += 1
                        else:
                            # 句子开始的状态
                            self.seg_P[tag] += 1
            
            # 计算概率
            total_sequences = sum(self.seg_P.values())
            for state in self.seg_states:
                self.seg_P[state] = (self.seg_P[state] + 1) / (total_sequences + len(self.seg_states))
                
                for state2 in self.seg_states:
                    self.seg_A[state][state2] = (trans_count[state][state2] + 1) / (state_count[state] + len(self.seg_states))
                
                for char in emit_count[state]:
                    self.seg_B[state][char] = emit_count[state][char] / state_count[state]
            
            return True, "训练成功"
            
        except Exception as e:
            return False, f"训练失败：{str(e)}"
    
    def viterbi(self, text):
        V = [{}]
        path = {}
        
        # 初始化
        for state in self.seg_states:
            V[0][state] = self.seg_P[state] * self.seg_B[state].get(text[0], 1e-10)
            path[state] = [state]
        
        # 动态规划
        for t in range(1, len(text)):
            V.append({})
            new_path = {}
            
            for curr_state in self.seg_states:
                max_prob, max_state = max(
                    (V[t-1][prev_state] * self.seg_A[prev_state][curr_state] * self.seg_B[curr_state].get(text[t], 1e-10), prev_state)
                    for prev_state in self.seg_states
                )
                
                V[t][curr_state] = max_prob
                new_path[curr_state] = path[max_state] + [curr_state]
            
            path = new_path
        
        prob, state = max((V[len(text) - 1][state], state) for state in self.seg_states)
        return path[state]
    
    def segment_and_tag(self, text):
        # 获取分词标签序列
        seg_tags = self.viterbi(text)
        
        # 分词结果
        words = []
        word = ''
        seg_result = ''  # 带空格的分词结果
        pos_result = ''  # 对应的词性标注结果
        
        for i, (char, tag) in enumerate(zip(text, seg_tags)):
            word += char
            seg_result += char
            # 这里我们使用分词标签作为词性标注
            pos_result += tag  # 使用分词标签作为词性标注
            
            if tag in ['E', 'S']:
                words.append(word)
                word = ''
                # 不是最后一个字符时，添加空格
                if i < len(text) - 1:
                    seg_result += ' '
                    pos_result += ' '
        
        if word:
            words.append(word)
        
        return seg_result, pos_result

class SegmentationGUI:
    def __init__(self, master):
        self.master = master
        self.master.title("中文分词与词性标注系统")
        self.master.geometry("800x600")
        
        self.segmenter = HMMSegmenterTagger()
        self.create_widgets()
    
    def create_widgets(self):
        # 训练文件选择框
        train_frame = ttk.LabelFrame(self.master, text="训练模型", padding="5")
        train_frame.pack(fill="x", padx=5, pady=5)
        
        ttk.Label(train_frame, text="训练文件：").grid(row=0, column=0, sticky="w")
        self.train_path = tk.StringVar()
        ttk.Entry(train_frame, textvariable=self.train_path, width=50).grid(row=0, column=1, padx=5)
        ttk.Button(train_frame, text="浏览", command=self.browse_file).grid(row=0, column=2)
        
        ttk.Button(train_frame, text="训练模型", command=self.train_model).grid(row=1, column=1, pady=5)
        
        # 文本输入区域
        input_frame = ttk.LabelFrame(self.master, text="输入文本", padding="5")
        input_frame.pack(fill="both", expand=True, padx=5, pady=5)
        
        self.input_text = scrolledtext.ScrolledText(input_frame, height=5)
        self.input_text.pack(fill="both", expand=True)
        
        ttk.Button(input_frame, text="分词和标注", command=self.process_text).pack(pady=5)
        
        # 结果显示区域
        result_frame = ttk.LabelFrame(self.master, text="处理结果", padding="5")
        result_frame.pack(fill="both", expand=True, padx=5, pady=5)
        
        self.result_text = scrolledtext.ScrolledText(result_frame, height=10)
        self.result_text.pack(fill="both", expand=True)
    
    def browse_file(self):
        filename = filedialog.askopenfilename(
            filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")]
        )
        if filename:
            self.train_path.set(filename)
    
    def train_model(self):
        file_path = self.train_path.get()
        if not file_path:
            messagebox.showerror("错误", "请选择训练文件")
            return
        
        success, message = self.segmenter.train(file_path)
        if success:
            messagebox.showinfo("成功", message)
        else:
            messagebox.showerror("错误", message)
    
    def process_text(self):
        text = self.input_text.get("1.0", tk.END).strip()
        if not text:
            messagebox.showerror("错误", "请输入待处理的文本")
            return
        
        try:
            # 分词和标注
            seg_result, pos_result = self.segmenter.segment_and_tag(text)
            
            # 显示结果
            self.result_text.delete("1.0", tk.END)
            self.result_text.insert("1.0", f"分词结果：\n{seg_result}\n\n词性标注：\n{pos_result}")
        except Exception as e:
            messagebox.showerror("错误", f"处理失败：{str(e)}")

def main():
    root = tk.Tk()
    app = SegmentationGUI(root)
    root.mainloop()

if __name__ == "__main__":
    main()